home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 January / macformat-020.iso / Shareware City / Developers / apps.to.go / DTS.Lib / =Using GWLayers.c < prev    next >
Encoding:
Text File  |  1994-05-04  |  14.1 KB  |  255 lines  |  [TEXT/MPS ]

  1. ***** GWLayers.c usage documentation *****/
  2.  
  3. Purpose:  To simplify and standardize offscreen drawing in a flexible way.
  4.  
  5. NOTE:  This package was originally written for use with GWorlds, but system 6
  6.        support has been added.  You no longer need system 7 or color quickdraw
  7.        to use this package.
  8.  
  9. Using offscreen GWorlds allows applications to achieve very clean graphics
  10. effects that are not possible if drawing directly to a window (or grafPort).
  11. In most cases, when an image is generated in an offscreen GWorld, that image
  12. will end up being displayed in a window.
  13.  
  14. This relationship between offscreen GWorlds and windows needs to be managed.
  15. In addition to one GWorld relating to a window, you may actually need multiple
  16. GWorlds to relate to a window.  Many effects demand more than one offscreen
  17. GWorld.  Smoothly dragging an image over a background is one such effect.
  18.  
  19. GWLayers.c is a block of code that manages the more mundane aspects of handling one
  20. or more GWorlds.  GWLayers.c gives you a consistent way of relating GWorlds/windows
  21. together.
  22.  
  23. A common use for offscreen GWorlds is to move an object around in a window smoothly
  24. over a background.  To accomplish this, we need 3 layers.
  25. These are:
  26.  
  27. 1) The window layer.  This is the top-most layer in the layer hierarchy.  The
  28.    top-most layer is typically what the user will see, and therefore commonly
  29.    is a window layer.  Layers below the top-most are typically offscreen
  30.    GWorlds.
  31. 2) A middle layer that is used to apply the object being moved to the
  32.    background plus removing the object from the old location.  Once these
  33.    two tasks are done, the offscreen work area is ready to be transferred
  34.    to the window layer.
  35. 3) A background image against which the object moves.  This is used to
  36.    restore the middle layer at the location where the object being moved
  37.    was last at.
  38.  
  39. The background layer is generated only once.  The relevant (changing) portions of
  40. the background image are copied into the middle layer.  Once the background portion
  41. is copied into the middle layer, the object that is moving across the background is
  42. drawn into the middle layer on top of the background portion.
  43.  
  44. Once the middle layer has the background portion with the moving object drawn onto it,
  45. the portion is transferred to the top-most layer, which is the window layer.  The user
  46. sees only this final transfer, so the intermediate steps are completely hidden.  The
  47. user just sees the object being dragged drawn into its new location.
  48.  
  49. To make the dragged object seem to move, it is also important to erase it from its
  50. old location.  If this were done in two steps (restore old location, draw in new location),
  51. the movement would flicker.  The user would be able to perceive (very easily) that
  52. the object is first removed from the old location and the redrawn in the new location.
  53. To prevent this flickering, the old location and the new location have to be updated
  54. in a single step.  Working offscreen allows this.  The middle layer is first updated so
  55. that the old location of the object is restored to background.  The middle layer then has
  56. the object drawn into the new location.  And then when the transfer finally occurs into
  57. the window layer, an area large enough to cover both the old location and the new location
  58. are transferred at once with a single CopyBits.
  59.  
  60.  
  61. GWLayers.c conventions:
  62.  
  63. Updating of each layer is handled by the layer itself.  A particular layer checks to
  64. see if there is a layer below, and if there is, does a CopyBits of the relevant area
  65. from the below layer into itself.  If after the CopyBits there is any additional
  66. drawing needed (such as the middle layer in the above example), then it is typically
  67. done after the CopyBits.
  68.  
  69. Since the layers actually need to update from the bottom-most layer to the top-most
  70. layer (top-most being the window, showing the final result), the layer updating code
  71. checks to see if there is a layer below.  If there is, then the update procedure is
  72. called for the layer below.  This recursion continues until the bottom-most layer is
  73. reached.  The bottom-most layer then does its thing.  (For the case where the bottom-most
  74. layer is background this "thing" will be to do nothing.)  Once the update for the layer
  75. is completed, the update procedure simply returns.  It will return to the caller, which
  76. is the layer above it (if any).  The next layer up does its thing, and then returns, and
  77. so on.  This recursive chain of layers allows the updating to happen automatically in
  78. the order designated by the layer hierarchy.
  79.  
  80. Since window records and GWorld records don't have any fields for all of the stuff we
  81. need to keep (such as above-layer, below-layer, update procedures, etc.), this
  82. implementation uses a handle to hold the layer record.  Each layer record has a reference
  83. to the window or GWorld is is to draw into, plus a reference to what layers records
  84. are above and below, if any.  The structure for a layer record is as follows:
  85.  
  86. typedef struct LayerRec {
  87.     LayerObj        aboveLayer;            /* Nil if no above layer. */
  88.     LayerObj        belowLayer;            /* Nil if no below layer. */
  89.     Boolean         layerOwnsPort;        /* True if layer created the GWorld. */
  90.     GrafPtr            layerPort;            /* Window or GWorld this layer draws into. */
  91.     GDHandle        layerGDevice;        /* The GDevice for this layer. */
  92.     Handle            layerBitmap;        /* If GWorlds aren't available, this holds the bitmap. */
  93.     short            layerDepth;            /* Requested NewLayer depth of pixmap. */
  94.     LayerProc        layerProc;            /* Layer procedure.  If nil, then default behaviors used. */
  95.     unsigned long    layerData;            /* Application refCon-type field. */
  96.     short            xferMode;            /* Transfer mode for CopyBits. */
  97.     Rect            srcRect;            /* Initially nil, which makes entire GWorld source. */
  98.     Rect            dstRect;            /* Initially nil, which makes entire window/GWorld dest. */
  99.     Rect            thisUpdate;            /* Area to be updated for this update. */
  100.     Rect            lastUpdate;            /* Area updated last time UpdateLayer() was called. */
  101.     Boolean            includeLastUpdate;    /* True is last updated area is o updated as well. */
  102.     Boolean            lockedCount;        /* Used internally by GWorld locking/unlocking calls. */
  103.     Boolean            cachedCount;        /* Used internally by GWorld locking/unlocking calls. */
  104.     CGrafPtr        cachedPort;            /* Used internally by GWorld usage calls. */
  105.     GDHandle        cachedGDevice;        /* Used internally by GWorld usage calls. */
  106. } LayerRec;
  107.  
  108. layerOwnsPort:
  109.     If the layer created the GWorld, then it is the layer's responsibility to get rid
  110.     of its GWorld when the layer is disposed of.  If this flag is true, the GWorld
  111.     will be disposed of when the layer is disposed of.
  112.  
  113. layerProc:
  114.     This is nil if the default behaviors are acceptable for a particular layer.
  115.     If this is non-nil, then you are responsible for implementing each layer
  116.     message behavior.  (More on this later.)
  117.  
  118. xferMode:
  119.     This is the transfer mode for CopyBits.  The default behavior for updating simply
  120.     transfers some or all of the below layer (if there is one) into the current layer.
  121.     It does this with CopyBits, using the designated transfer mode.
  122.  
  123. srcRect:
  124.     This is initially an empty rect.  If this srcRect is empty, then the portRect of the
  125.     GWorld is used as the srcRect.  This means the default srcRect is the entire GWorld.
  126.     If this field is set to a non-empty rect, the default update behavior is to do a CopyBits
  127.     of just the srcRect area, and not the entire GWorld.  (Remember, the CopyBits is done by
  128.     the above layer, so only if the above layer uses the default update behavior does this
  129.     occur automatically.)
  130.     Having a srcRect smaller than the entire GWorld allows for additional effects, such as
  131.     fat-bits.  If the srcRect for this layer is smaller than the dstRect of the above layer,
  132.     CopyBits will transfer a small rect into a big rect.  The image will be scaled to
  133.     accommodate the larger destination rect, and presto -- fat-bits!!
  134.  
  135. dstRect:
  136.     This field is initially an empty rect, just like srcRect.  If dstRect is set to a
  137.     non-empty rect, then the default update behavior will CopyBits only into this area.
  138.     This allows targeting of a specific portion of a window for offscreen updating, while
  139.     the rest of the window can be used from scrollbars, or some such other thing.
  140.  
  141. thisRect:
  142.     This field is also initially an empty rect.  This field becomes non-empty by
  143.     calling InvalLayer().  InvalLayer() unions the rect passed to it into thisRect and
  144.     stores the result in thisRect.  It then calls itself if there is a below layer, thus
  145.     recursively changing thisRect for all layers below it in the layer hierarchy.
  146.  
  147. lastRect:
  148.     This field is also initially an empty rect.  This field becomes non-empty after
  149.     thisRect is non-empty and then UpdateLayer() is called.  Once the update is done,
  150.     thisRect is copied into lastRect, and then thisRect is set empty once again.
  151.     The lastRect field is use to optionally combine updating the last update with the
  152.     current update.  This is particularly useful for dragging objects, as described above.
  153.     In a single CopyBits, the old location and the new location need to be drawn in the
  154.     window.  By setting includeLastUpdate true, this is done automatically by the default
  155.     update behavior.
  156.  
  157. includeLastUpdate:
  158.     As just described for the lastRect field, if this field is true, then the default
  159.     update behavior CopyBits includes both the last update and this update, thus generating
  160.     smooth animation effects.
  161.  
  162.  
  163. A call to create a layer for a window (the top-most layer, most likely) might look like this:
  164.  
  165.     NewLayer(&windowLayer,    /* Layer object handle is returned here.    */
  166.               nil,            /* Top layer, so there is no above layer.   */
  167.               nil,            /* Uses default layer procedure.            */
  168.               window,         /* Window used by the layer object.         */
  169.               0,              /* Depth for offscreen 0 for screen depth). */
  170.               0);             /* Custom layer init data, if any.          */
  171.  
  172. If NewLayer succeeds, the layer object handle is returned in windowLayer.  If it fails,
  173. nil is returned in windowLayer, plus an error is returned.  This call can hardly fail, as
  174. there is no offscreen GWorld created.  If only a minimally-sized handle can be created, it
  175. will succeed.
  176.  
  177. To create a work layer below this window layer, we might make a call such as:
  178.  
  179.     err = NewLayer(&workLayer, windowLayer, WorkLayerProc, nil, 0, (long)&myInfo);
  180.             /* myInfo is a struct that holds information that WorkLayerProc
  181.             ** needs to reference for drawing the object in the new location. */
  182.  
  183. This call has a greater chance of failure, as an offscreen GWorld was created for it.
  184. We passed it a nil grafPort, so the default initialization behavior knows that it will
  185. have to create a GWorld.  The default initialization behavior uses dstRect of the above
  186. layer as the size of the offscreen GWorld it is to create.  If dstRect is an empty rect,
  187. then it uses the portRect of the above layer instead.  So if you wanted to map the offscreen
  188. layer to just a portion of the window, you would set the dstRect for the top-most layer
  189. prior to creating the middle layer.
  190.  
  191. IMPORTANT:  The default behavior automatically creates the offscreen GWorld to the described
  192.             size.  We have passed NewLayer() a layer procedure, so we don't automatically get
  193.             the default behaviors for the middle layer.  This must be kept in mind when
  194.             writing the layer procedure for the middle layer.  To get the default behavior,
  195.             we simply call DefaultLayerInit() when the layer procedure gets an initialization
  196.             message.  Since the default initialization behavior is exactly what we want, we
  197.             just call it directly.
  198.  
  199. The reason that we have a layer procedure for this layer is that we want to do something
  200. different when we receive an update message.  For the update message we want to first
  201. transfer the portion of background into the middle layer, and then we want to draw the object
  202. we are dragging into the middle layer.  For the update message, we can first call
  203. DefaultLayerUpdate() to do the CopyBits from the below layer into the middle layer, and then
  204. right after that we can draw the object being dragged into its new location.  The neat thing
  205. about this is that the default behavior can still commonly be used, even if there is extra
  206. stuff that we need to do.
  207.  
  208. Assuming no errors, we now have a top-most layer relating to the target window, plus a middle
  209. layer for doing the offscreen drawing of the object being dragged.  We now need a background
  210. layer that has an image of the background drawn into it.  This code might look like:
  211.  
  212.     if (!err) err = NewLayer(&backLayer, workLayer, BackLayerProc, nil, 0, (long)&myInfo);
  213.  
  214. This layer also has its own layer procedure.  The reasoning behind this is that the
  215. initialization would do the drawing into the background.  Once the layers are all set up,
  216. we want the background layer to be fully imaged.  This way there is no time spent for the
  217. background layer, which is the way a background should be.
  218.  
  219. To recap, the code to create the 3 layers is as follows:
  220.  
  221.                     NewLayer(&windowLayer, nil, nil, window, 0, 0);
  222.               err = NewLayer(&workLayer, windowLayer, WorkLayerProc, nil, 0, (long)&myInfo);
  223.     if (!err) err = NewLayer(&backLayer, workLayer, BackLayerProc, nil, 0, (long)&myInfo);
  224.  
  225. For dragging, we would then have some sort of loop, such as:
  226.  
  227.     /* Assume the location where the user clicked is already in mouseLoc, local coordinates. */
  228.  
  229.     if (err) return;        /* No ram for the offscreen stuff. */
  230.  
  231.     lastLoc.h = lastLoc.v = -32767;        /* Unclickable initial point. */
  232.     while (StillDown()) {
  233.         if ((lastLoc.h != mouseLoc.h) || (lastLoc.v != mouseLoc.v)) {    /* Always true first time. */
  234.             rct.top  = rct.bottom = mouseLoc.v;
  235.             rct.left = rct.right  = mouseLoc.h;
  236.             rct.bottom += myInfo.objectHeight;
  237.             rct.right  += myInfo.objectWidth;
  238.             InvalLayer(windowLayer, rct, true);        /* Update last location, as well as this one. */
  239.             UpdateLayer(windowLayer);
  240.             lastLoc = mouseLoc;
  241.         }
  242.     }
  243.  
  244.     DisposeThisAndBelowLayers(windowLayer);
  245.  
  246. As can be seen above, smoothly dragging an object can be very little code.  There are a few
  247. things not handled by the above code, such as if there isn't enough memory for the offscreen
  248. GWorlds.  It isn't really polite to do nothing, as the above code does.  For exception handling,
  249. please see the actual sample code that uses the GWLayers.c package.
  250.  
  251.  
  252. For explanations of the remaining functions of GWLayers.c, see the header file GWLayers.h, which
  253. is in the DTS.Lib.headers folder.
  254.  
  255.